/**
 * \file packed_type.hpp
 * 
 * \section COPYRIGHT
 *
 * Basis: A 3D Mathematics Library
 *
 * ---------------------------------------------------------------------
 *
 * Copyright (c) 2010, Don Olmstead
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *  3. Neither the name of organization nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef BASIS_DETAIL_PACKED_TYPE_HPP_INCLUDED
#define BASIS_DETAIL_PACKED_TYPE_HPP_INCLUDED

#include <basis/detail/config.hpp>
#include <cmath>

namespace basis { namespace detail
{
	/**
	 * A real number type emulating a hardware SIMD datatype.
	 *
	 * \tparam Real A real number type.
	 *
	 * \author Don Olmstead
	 * \version 0.1
	 */
	template <typename Real>
	class packed_type
	{
		public:

			//------------------------------------------------------------
			// Member types
			//------------------------------------------------------------

			/// A real number type
			typedef Real value_type;
			/// Reference to a real number type
			typedef Real& reference;
			/// Unsigned integral type
			typedef std::size_t size_type;
			/// Mask containing the results of a comparison
			typedef int comparison_mask;

			//------------------------------------------------------------
			// Construction/Destruction
			//------------------------------------------------------------

			/**
			 * Creates an in instance of the packed_type class.
			 *
			 * Left unitinitialized to speed construction.
			 */
			inline packed_type()
			{ }

			/**
			 * Initializes an instance of the packed_type class.
			 *
			 * \param value1 The 1st component.
			 * \param value2 The 2nd component.
			 * \param value3 The 3rd component.
			 * \param value4 The 4th component.
			 */
			inline packed_type(const value_type value1, const value_type value2, const value_type value3, const value_type value4)
			{
				_values[0] = value1;
				_values[1] = value2;
				_values[2] = value3;
				_values[3] = value4;
			}

			/**
			 * Initializes an instance of the packed_type class.
			 *
			 * \param value The value to assign to all the components.
			 */
			inline packed_type(const value_type value)
			{
				_values[0] = value;
				_values[1] = value;
				_values[2] = value;
				_values[3] = value;
			}

			/**
			 * Initializes an instance of the packed_type class.
			 *
			 * \param values An array containing the values for the components.
			 */
			inline packed_type(const value_type* values)
			{
				_values[0] = values[0];
				_values[1] = values[1];
				_values[2] = values[2];
				_values[3] = values[3];
			}

			/**
			 * Initializes an instance of the packed_type class.
			 *
			 * \param copy The instance to copy.
			 */
			inline packed_type(const packed_type& copy)
			{
				_values[0] = copy._values[0];
				_values[1] = copy._values[1];
				_values[2] = copy._values[2];
				_values[3] = copy._values[3];
			}

			//------------------------------------------------------------
			// Assignment
			//------------------------------------------------------------
			
			/**
			 * Assignment operator for the packed_type class.
			 *
			 * \param copy The instance to copy.
			 */
			inline packed_type& operator= (const packed_type& copy)
			{
				_values[0] = copy._values[0];
				_values[1] = copy._values[1];
				_values[2] = copy._values[2];
				_values[3] = copy._values[3];

				return *this;
			}

			//------------------------------------------------------------
			// Element access
			//------------------------------------------------------------

			/**
			 * Element access for the packed_type.
			 *
			 * \param i The index of the element.
			 * \returns The value of the element.
			 */
			inline value_type operator[] (size_type i) const
			{
				BASIS_ASSERT(i < 4, "Index out of range!");

				return _values[i];
			}

			/**
			 * Element access for the packed_type.
			 *
			 * \param i The index of the element.
			 * \returns The value of the element.
			 */
			inline reference operator[] (size_type i)
			{
				BASIS_ASSERT(i < 4, "Index out of range!");

				return _values[i];
			}

			/**
			 * Sets all the values of the sse_type.
			 *
			 * \param value1 The 1st component.
			 * \param value2 The 2nd component.
			 * \param value3 The 3rd component.
			 * \param value4 The 4th component.
			 */
			inline void set_values(const value_type value1, const value_type value2, const value_type value3, const value_type value4)
			{
				_values[0] = value1;
				_values[1] = value2;
				_values[2] = value3;
				_values[3] = value4;
			}

			/**
			 * Accessor for the size of the packed_type.
			 *
			 * Size is the number of elements that can be accessed
			 * within the type.
			 *
			 * \returns The size of the packed_type.
			 */
			inline static size_type size()
			{
				return 4;
			}

			/**
			 * Accessor for the capacity of the packed_type.
			 *
			 * Capacity is the number of elements that could be
			 * stored in the type.
			 *
			 * \returns The capacity of the packed_type.
			 */
			inline static size_type capacity()
			{
				return 4;
			}

			//------------------------------------------------------------
			// Comparison operations
			//------------------------------------------------------------

			/// Flags for comparison results
			enum comparison_flags
			{
				/// Results of the comparison at the 1st element
				true_0 = 0x01,
				/// Results of the comparison at the 2nd element
				true_1 = 0x02,
				/// Results of the comparison at the 3rd element
				true_2 = 0x04,
				/// Results of the comparison at the 4th element
				true_3 = 0x08
			} ;

			/**
			 * Returns a comparison flag containing all values.
			 *
			 * \returns A comparison flag containing all values.
			 */
			inline static comparison_flags mask_all()
			{
				return (comparison_flags)(true_0 | true_1 | true_2 | true_3);
			}

			/**
			 * Compares the components of two packed_types for equality.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask equal(const packed_type& lhs, const packed_type& rhs)
			{
				return
					((lhs._values[0] == rhs._values[0]))      |
					((lhs._values[1] == rhs._values[1]) << 1) |
					((lhs._values[2] == rhs._values[2]) << 2) |
					((lhs._values[3] == rhs._values[3]) << 3) ;
			}

			/**
			 * Compares the components of two packed_types for inequality.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask not_equal(const packed_type& lhs, const packed_type& rhs)
			{
				return
					((lhs._values[0] != rhs._values[0]))      |
					((lhs._values[1] != rhs._values[1]) << 1) |
					((lhs._values[2] != rhs._values[2]) << 2) |
					((lhs._values[3] != rhs._values[3]) << 3) ;
			}

			/**
			 * Performs a less than comparison on the components of two packed_types.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask less_than(const packed_type& lhs, const packed_type& rhs)
			{
				return
					((lhs._values[0] < rhs._values[0]))      |
					((lhs._values[1] < rhs._values[1]) << 1) |
					((lhs._values[2] < rhs._values[2]) << 2) |
					((lhs._values[3] < rhs._values[3]) << 3) ;
			}

			/**
			 * Performs a less than or equal to comparison on the components of two packed_types.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask less_than_equal(const packed_type& lhs, const packed_type& rhs)
			{
				return
					((lhs._values[0] <= rhs._values[0]))      |
					((lhs._values[1] <= rhs._values[1]) << 1) |
					((lhs._values[2] <= rhs._values[2]) << 2) |
					((lhs._values[3] <= rhs._values[3]) << 3) ;
			}

			/**
			 * Performs a greater than comparison on the components of two packed_types.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask greater_than(const packed_type& lhs, const packed_type& rhs)
			{
				return
					((lhs._values[0] > rhs._values[0]))      |
					((lhs._values[1] > rhs._values[1]) << 1) |
					((lhs._values[2] > rhs._values[2]) << 2) |
					((lhs._values[3] > rhs._values[3]) << 3) ;
			}

			/**
			 * Performs a greater than or equal to comparison on the components of two packed_types.
			 *
			 * \param lhs Source type.
			 * \param rhs Source type.
			 * \returns A mask containing the results of the comparison.
			 */
			inline static comparison_mask greater_than_equal(const packed_type& lhs, const packed_type& rhs)
			{
				return
					((lhs._values[0] >= rhs._values[0]))      |
					((lhs._values[1] >= rhs._values[1]) << 1) |
					((lhs._values[2] >= rhs._values[2]) << 2) |
					((lhs._values[3] >= rhs._values[3]) << 3) ;
			}

			//------------------------------------------------------------
			// Arithmetic operators
			//------------------------------------------------------------

			/**
			 * Negates the packed_type.
			 *
			 * \returns The negation of the type.
			 */
			inline packed_type operator- () const
			{
				return packed_type(
					-_values[0],
					-_values[1],
					-_values[2],
					-_values[3]);
			}

			/**
			 * Adds two packed_types.
			 *
			 * \param rhs Source type.
			 * \returns The sum of the two packed_types.
			 */
			inline packed_type operator+ (const packed_type& rhs) const
			{
				return packed_type(
					_values[0] + rhs._values[0],
					_values[1] + rhs._values[1],
					_values[2] + rhs._values[2],
					_values[3] + rhs._values[3]);
			}

			/**
			 * Subtracts a packed_type from a packed_type.
			 *
			 * \param rhs Source type.
			 * \returns The difference of the two packed_types.
			 */
			inline packed_type operator- (const packed_type& rhs) const
			{
				return packed_type(
					_values[0] - rhs._values[0],
					_values[1] - rhs._values[1],
					_values[2] - rhs._values[2],
					_values[3] - rhs._values[3]);
			}

			/**
			 * Multiplies a packed_type by a packed_type.
			 *
			 * \param rhs Source type.
			 * \returns The product of the two packed_types.
			 */
			inline packed_type operator* (const packed_type& rhs) const
			{
				return packed_type(
					_values[0] * rhs._values[0],
					_values[1] * rhs._values[1],
					_values[2] * rhs._values[2],
					_values[3] * rhs._values[3]);
			}

			/**
			 * Divides a packed_type by a packed_type.
			 *
			 * \param rhs Source type.
			 * \returns The quotient of the two packed_types.
			 */
			inline packed_type operator/ (const packed_type& rhs) const
			{
				return packed_type(
					_values[0] / rhs._values[0],
					_values[1] / rhs._values[1],
					_values[2] / rhs._values[2],
					_values[3] / rhs._values[3]);
			}

			/**
			 * Adds two packed_types.
			 *
			 * \param rhs Source type.
			 * \returns The sum of the two packed_types.
			 */
			inline packed_type& operator+= (const packed_type& rhs)
			{
				_values[0] += rhs._values[0];
				_values[1] += rhs._values[1];
				_values[2] += rhs._values[2];
				_values[3] += rhs._values[3];

				return *this;
			}

			/**
			 * Subtracts a packed_type from a packed_type.
			 *
			 * \param rhs Source type.
			 * \returns The difference of the two packed_types.
			 */
			inline packed_type& operator-= (const packed_type& rhs)
			{
				_values[0] -= rhs._values[0];
				_values[1] -= rhs._values[1];
				_values[2] -= rhs._values[2];
				_values[3] -= rhs._values[3];

				return *this;
			}

			/**
			 * Multiplies a packed_type by a packed_type.
			 *
			 * \param rhs Source type.
			 * \returns The product of the two packed_types.
			 */
			inline packed_type& operator*= (const packed_type& rhs)
			{
				_values[0] *= rhs._values[0];
				_values[1] *= rhs._values[1];
				_values[2] *= rhs._values[2];
				_values[3] *= rhs._values[3];

				return *this;
			}

			/**
			 * Divides a packed_type by a packed_type.
			 *
			 * \param rhs Source type.
			 * \returns The quotient of the two packed_types.
			 */
			inline packed_type& operator/= (const packed_type& rhs)
			{
				_values[0] /= rhs._values[0];
				_values[1] /= rhs._values[1];
				_values[2] /= rhs._values[2];
				_values[3] /= rhs._values[3];

				return *this;
			}

			//------------------------------------------------------------
			// Arithmetic operations
			//------------------------------------------------------------

			/**
			 * Takes the inverse of an packed_type.
			 *
			 * \code
			 * results[0] = 1.0 / a[0];
			 * results[1] = 1.0 / a[1];
			 * results[2] = 1.0 / a[2];
			 * results[3] = 1.0 / a[3];
			 * \endcode
			 *
			 * \param a Source type.
			 * \returns The inverse of a packed_type.
			 */
			inline static const packed_type inv(const packed_type& a)
			{
				return packed_type(
					(value_type)1 / a._values[0],
					(value_type)1 / a._values[1],
					(value_type)1 / a._values[2],
					(value_type)1 / a._values[3]);
			}

			/**
			 * Takes the sqrt of an packed_type.
			 *
			 * \code
			 * results[0] = sqrt(a[0]);
			 * results[1] = sqrt(a[1]);
			 * results[2] = sqrt(a[2]);
			 * results[3] = sqrt(a[3]);
			 * \endcode
			 * 
			 * \param a Source type.
			 * \returns The sqrt of a packed_type.
			 */
			inline static const packed_type sqrt(const packed_type& a)
			{
				return packed_type(
					std::sqrt(a._values[0]),
					std::sqrt(a._values[1]),
					std::sqrt(a._values[2]),
					std::sqrt(a._values[3]));
			}

			/**
			 * Takes the inverse sqrt of an packed_type.
			 *
			 * \code
			 * results[0] = 1.0f / sqrt(a[0]);
			 * results[1] = 1.0f / sqrt(a[1]);
			 * results[2] = 1.0f / sqrt(a[2]);
			 * results[3] = 1.0f / sqrt(a[3]);
			 * \endcode
			 *
			 * \param a Source type.
			 * \returns The inverse sqrt of a packed_type.
			 */
			inline static const packed_type inv_sqrt(const packed_type& a)
			{
				return packed_type(
					(value_type)1 / std::sqrt(a._values[0]),
					(value_type)1 / std::sqrt(a._values[1]),
					(value_type)1 / std::sqrt(a._values[2]),
					(value_type)1 / std::sqrt(a._values[3]));
			}

			/**
			 * Takes the component-wise minimum of two packed_types.
			 *
			 * \code
			 * results[0] = (a[0] < b[0]) ? a[0] : b[0];
			 * results[1] = (a[1] < b[1]) ? a[1] : b[1];
			 * results[2] = (a[2] < b[2]) ? a[2] : b[2];
			 * results[3] = (a[3] < b[3]) ? a[3] : b[3];
			 * \endcode
			 *
			 * \param a Source type.
			 * \param b Source type.
			 * \returns The component-wise minimum of two packed_types.
			 */
			inline static const packed_type min(const packed_type& a, const packed_type& b)
			{
				return packed_type(
					(a._values[0] < b._values[0]) ? a._values[0] : b._values[0],
					(a._values[1] < b._values[1]) ? a._values[1] : b._values[1],
					(a._values[2] < b._values[2]) ? a._values[2] : b._values[2],
					(a._values[3] < b._values[3]) ? a._values[3] : b._values[3]);
			}

			/**
			 * Takes the component-wise maximum of two packed_types.
			 *
			 * \code
			 * results[0] = (a[0] > b[0]) ? a[0] : b[0];
			 * results[1] = (a[1] > b[1]) ? a[1] : b[1];
			 * results[2] = (a[2] > b[2]) ? a[2] : b[2];
			 * results[3] = (a[3] > b[3]) ? a[3] : b[3];
			 * \endcode
			 *
			 * \param a Source type.
			 * \param b Source type.
			 * \returns The component-wise maximum of two packed_types.
			 */
			inline static const packed_type max(const packed_type& a, const packed_type& b)
			{
				return packed_type(
					(a._values[0] > b._values[0]) ? a._values[0] : b._values[0],
					(a._values[1] > b._values[1]) ? a._values[1] : b._values[1],
					(a._values[2] > b._values[2]) ? a._values[2] : b._values[2],
					(a._values[3] > b._values[3]) ? a._values[3] : b._values[3]);
			}

			//------------------------------------------------------------
			// Horizontal operations
			//------------------------------------------------------------

			/**
			 * Performs a horizontal addition.
			 *
			 * A horizontal addition means that adjacent elements in the same operand are added
			 * together. The following code illustrates the operation.
			 *
			 * \code
			 * result[0] = a[0] + a[1];
			 * result[1] = a[2] + a[3];
			 * result[2] = b[0] + b[1];
			 * result[3] = b[2] + b[3];
			 * \endcode
			 *
			 * \param a The first operand.
			 * \param b The second operand.
			 */
			inline static const packed_type hadd(const packed_type& a, const packed_type& b)
			{
				return packed_type(
					a._values[0] + a._values[1],
					a._values[2] + a._values[3],
					b._values[0] + b._values[1],
					b._values[2] + b._values[3]);
			}

			/**
			 * Performs a horizontal subtraction.
			 *
			 * A horizontal subtraction means that adjacent elements in the same operand are added
			 * together. The following code illustrates the operation.
			 *
			 * \code
			 * result[0] = a[0] - a[1];
			 * result[1] = a[2] - a[3];
			 * result[2] = b[0] - b[1];
			 * result[3] = b[2] - b[3];
			 * \endcode
			 *
			 * \param a The first operand.
			 * \param b The second operand.
			 * \returns An packed_type containing the result of the horizontal operation.
			 */
			inline static const packed_type hsub(const packed_type& a, const packed_type& b)
			{
				return packed_type(
					a._values[0] - a._values[1],
					a._values[2] - a._values[3],
					b._values[0] - b._values[1],
					b._values[2] - b._values[3]);
			}

			//------------------------------------------------------------
			// Vector operations
			//------------------------------------------------------------

			/**
			 * Calculates the dot product of two packed_types.
			 *
			 * Calculates the dot product of a 2D vector.
			 *
			 * \param value1 Source vector.
			 * \param value2 Source vector.
			 * \returns The dot product of two packed_types.
			 */
			inline static const packed_type dot2(const packed_type& value1, const packed_type& value2)
			{
				return packed_type(
					(value1._values[0] * value2._values[0]) +
					(value1._values[1] * value2._values[1]));
			}

			/**
			 * Calculates the dot product of two packed_types.
			 *
			 * Calculates the dot product of a 3D vector.
			 *
			 * \param value1 Source vector.
			 * \param value2 Source vector.
			 * \returns The dot product of two packed_types.
			 */
			inline static const packed_type dot3(const packed_type& value1, const packed_type& value2)
			{
				return packed_type(
					(value1._values[0] * value2._values[0]) +
					(value1._values[1] * value2._values[1]) +
					(value1._values[2] * value2._values[2]));
			}

			/**
			 * Calculates the dot product of two packed_types.
			 *
			 * Calculates the dot product of a 4D vector.
			 *
			 * \param value1 Source vector.
			 * \param value2 Source vector.
			 * \returns The dot product of two packed_types.
			 */
			inline static const packed_type dot4(const packed_type& value1, const packed_type& value2)
			{
				return packed_type(
					(value1._values[0] * value2._values[0]) +
					(value1._values[1] * value2._values[1]) +
					(value1._values[2] * value2._values[2]) +
					(value1._values[3] * value2._values[3]));
			}

			//------------------------------------------------------------
			// Shuffle operations
			//------------------------------------------------------------

			/**
			 * Joins values from two packed_types.
			 *
			 * \code
			 * result[0] = a[_0];
			 * result[1] = a[_1];
			 * result[2] = b[_2];
			 * result[3] = b[_3];
			 * \endcode
			 *
			 * \tparam _0 The element from a to select for the 1st slot.
			 * \tparam _1 The element from a to select for the 2nd slot.
			 * \tparam _2 The element from b to select for the 3rd slot.
			 * \tparam _3 The element from b to select for the 4th slot.
			 *
			 * \param a Source type.
			 * \param b Source type.
			 * \returns The result of the shuffle operation.
			 */
			template <size_type _0, size_type _1, size_type _2, size_type _3>
			inline static const packed_type shuffle(const packed_type& a, const packed_type& b)
			{
				return packed_type(
					a._values[_0],
					a._values[_1],
					b._values[_2],
					b._values[_3]);
			}

			/**
			 * Shuffles the location of elements in the packed_type.
			 *
			 * \code
			 * result[0] = a[_0];
			 * result[1] = a[_1];
			 * result[2] = a[_2];
			 * result[3] = a[_3];
			 * \endcode
			 *
			 * \tparam _0 The element to select for the 1st slot.
			 * \tparam _1 The element to select for the 2nd slot.
			 * \tparam _2 The element to select for the 3rd slot.
			 * \tparam _3 The element to select for the 4th slot.
			 *
			 * \returns The result of the shuffle operation.
			 */
			template <size_type _0, size_type _1, size_type _2, size_type _3>
			inline const packed_type shuffle() const
			{
				return packed_type(
					_values[_0],
					_values[_1],
					_values[_2],
					_values[_3]);
			}

		private:

			/// Internal representation of the type
			value_type _values[4];

	} ; // end class packed_type<float>

} } // end namespace basis::detail

#endif // end BASIS_DETAIL_PACKED_TYPE_HPP_INCLUDED
